package org.lc.cmd.impl;

import lombok.extern.slf4j.Slf4j;
import org.jline.reader.Candidate;
import org.jline.reader.LineReader;
import org.jline.reader.ParsedLine;
import org.lc.cmd.CmdParam;
import org.lc.cmd.Command;
import org.lc.cmd.DataCompleter;
import org.lc.cmd.IndexParam;
import org.lc.cmd.OptParam;
import org.lc.cmd.ParamsParser;
import org.lc.cmd.exception.MissParamDataException;
import org.lc.cmd.exception.MissParamException;
import org.lc.cmd.exception.RepeatParamException;
import org.lc.cmd.exception.UnknownOptException;
import org.lc.cmd.exception.UnknownParamException;
import org.lc.cmd.impl.completer.CompleterManager;
import org.lc.cmd.util.CollectionUtil;
import org.lc.cmd.util.ConvertUtil;
import org.lc.cmd.util.StringUtil;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

@Slf4j
public class DefaultParamsParser implements ParamsParser {
    /**
     * key: command name
     * value: 所有index param，按下标升序排列，且从0开始依次+1
     */
    protected static final Map<Command, List<IndexParam>> indexParamMap = new ConcurrentHashMap<>();

    /**
     * 多次取值参数
     * key: command name
     * value: 所有多次取值的index param，按下标升序排序
     */
    protected static final Map<Command, List<IndexParam>> multipleIndexParamMap = new HashMap<>();

    /**
     * 短选项参数
     * key: command name
     * value: |
     * key: 选项
     * value: 参数
     */
    protected static final Map<Command, Map<Character, OptParam>> shortOptParamMap = new HashMap<>();

    /**
     * 长选项参数
     * key: command name
     * value: |
     * key: 选项
     * value: 参数
     */
    protected static final Map<Command, Map<String, OptParam>> longOptParamMap = new HashMap<>();

    protected static void loadCommandIfNotExist(Command command) {
        if (!indexParamMap.containsKey(command)) {
            synchronized (DefaultParamsParser.class) {
                if (!indexParamMap.containsKey(command)) {
                    reloadCommand(command);
                }
            }
        }
    }

    protected static void reloadCommand(Command command) {
        List<IndexParam> indexParams = new ArrayList<>();
        List<IndexParam> multipleIndexParams = new ArrayList<>();
        Map<Character, OptParam> shortOptParams = new HashMap<>();
        Map<String, OptParam> longOptParams = new HashMap<>();
        for (CmdParam param : command.getParams()) {
            if (param instanceof IndexParam) {
                IndexParam indexParam = (IndexParam) param;
                indexParams.add(indexParam);
                if (indexParam.isMultiple()) {
                    multipleIndexParams.add(indexParam);
                }
            } else if (param instanceof OptParam) {
                OptParam optParam = (OptParam) param;
                if (optParam.getShortOpts() != null) {
                    optParam.getShortOpts().forEach(opt -> shortOptParams.put(opt, optParam));
                }
                if (optParam.getLongOpts() != null) {
                    optParam.getLongOpts().forEach(opt -> longOptParams.put(opt, optParam));
                }
            }
        }
        indexParams.sort(Comparator.comparingInt(IndexParam::getIndex));
        multipleIndexParams.sort(Comparator.comparingInt(IndexParam::getIndex));
        indexParamMap.put(command, indexParams);
        multipleIndexParamMap.put(command, multipleIndexParams);
        shortOptParamMap.put(command, shortOptParams);
        longOptParamMap.put(command, longOptParams);
    }

    /**
     * 补全短选项
     *
     * @return 是否所有短选项全部已使用
     */
    private static boolean completeShortOptKey(SolonCommand cmd, SolonCommand.SolonMethodCommand subCmd,
            ParsedLine line,
            List<Candidate> candidates, Set<String> usedLongOpts,
            Set<Character> usedShortOpts) {
        // 待补全的参数
        Map<Character, OptParam> cmpParams = new HashMap<>();
        shortOptParamMap.getOrDefault(subCmd, Collections.emptyMap()).forEach((opt, p) -> {
            if (p.isMultiple()
                    || (!usedShortOpts.contains(opt) && p.getLongOpts().stream().noneMatch(usedLongOpts::contains))) {
                cmpParams.put(opt, p);

            }
        });
        cmpParams.forEach((opt, p) -> {
            String realOpt = (StringUtil.isEmpty(line.word()) ? "-" : "") + opt + (p.isWithParamData() ? "=" : "");
            candidates.add(new Candidate(line.word() + realOpt, realOpt, null, p.getDescription(), null, p.getName(),
                    !p.isWithParamData() && cmpParams.size() == 1));
        });
        // 没有可用参数，说明短选项已全部使用，当前单词可以完成了
        return cmpParams.isEmpty() && !line.word().isEmpty();
    }

    private static void completeLongOptKey(SolonCommand cmd, SolonCommand.SolonMethodCommand subCmd, ParsedLine line,
            List<Candidate> candidates,
            String arg, Set<String> usedLongOpts, Set<Character> usedShortOpts) {
        String argOpt = arg.startsWith("--") ? arg.substring(2) : "";
        longOptParamMap.getOrDefault(subCmd, Collections.emptyMap()).forEach((opt, p) -> {
            if (opt.startsWith(argOpt) && (p.isMultiple()
                    || (!usedLongOpts.contains(opt)) && p.getShortOpts().stream().noneMatch(usedShortOpts::contains))) {
                String realOpt = "--" + opt + (p.isWithParamData() ? "=" : "");
                candidates.add(new Candidate(realOpt, realOpt, null, p.getDescription(), null, p.getName(),
                        !p.isWithParamData()));
            }
        });
    }

    @Override
    public Map<String, Object> parse(Command command, String[] args) {
        loadCommandIfNotExist(command);
        Map<String, Object> realParams = parse(command, Arrays.asList(args), args.length);
        // 设置默认值，检查缺少的参数
        for (CmdParam param : command.getParams()) {
            if (param.getDefaultValue() != null) {
                realParams.putIfAbsent(param.getName(), param.getDefaultValue());
            } else if (param instanceof OptParam && !((OptParam) param).isWithParamData()) {
                realParams.putIfAbsent(param.getName(), false);
            }
            if (param.isRequired() && !realParams.containsKey(param.getName())) {
                throw new MissParamException(command.getName(), param);
            }
        }
        return realParams;
    }

    private Map<String, Object> parse(Command command, List<String> args, int nowIndex) {
        Map<String, Object> realParams = new HashMap<>();
        // index param的下标
        int index = 0;
        for (int i = 0; i < args.size(); i++) {
            String arg = args.get(i);
            if ((nowIndex - 1 == i && optNeedData(command, arg)) || nowIndex == i) {
                // 当前输入作为短选项参数时，解析其他短选项
                if (arg.startsWith("-") && !arg.startsWith("--")) {
                    parseShortOptWithoutLast(command, arg, realParams);
                }
            } else if (arg.startsWith("--")) {
                i = parseLongOpt(command, args, i, realParams);
            } else if (arg.startsWith("-")) {
                i = parseShortOpt(command, args, i, realParams);
            } else {
                IndexParam param = findIndexParam(command, index++, arg);
                if (param != null) {
                    setValue(command, realParams, param, arg);
                }
            }
        }
        return realParams;
    }

    private boolean optNeedData(Command command, String arg) {
        if (!arg.startsWith("-")) {
            return false;
        }
        if (arg.contains("=")) {
            return false;
        }
        OptParam param;
        if (arg.startsWith("--")) {
            param = findParamByLongOpt(command, arg.substring(2));
        } else {
            param = findParamByShortOpt(command, arg.charAt(arg.length() - 1));
        }
        if (param != null) {
            return param.isWithParamData();
        }
        return false;
    }

    private void parseShortOptWithoutLast(Command command, String arg, Map<String, Object> realParams) {
        int endIndex = arg.indexOf('=');
        if (endIndex == -1) {
            endIndex = arg.length();
        }
        endIndex--;
        for (int j = 1; j < endIndex; j++) {
            OptParam param = findParamByShortOpt(command, arg.charAt(j));
            if (param != null) {
                setValue(command, realParams, param, "true");
            }
        }
    }

    private int parseShortOpt(Command command, List<String> args, int i, Map<String, Object> realParams) {
        if (args.get(i).length() <= 1) {
            if (!command.getConfig().isIgnoreUnknownOpt()) {
                throw new UnknownOptException(command, args.get(i));
            }
            return i;
        }
        String arg = args.get(i).substring(1);
        if (arg.contains("=")) {
            String[] kv = StringUtil.splitKv(arg, "=");
            assert kv != null;
            arg = kv[0].substring(0, kv[0].length() - 1);
            char key = kv[0].charAt(kv[0].length() - 1);
            OptParam param = findParamByShortOpt(command, key);
            if (param != null) {
                setValue(command, realParams, param, kv[1]);
            }
        }
        for (int j = 0; j < arg.length(); j++) {
            char key = arg.charAt(j);
            OptParam param = findParamByShortOpt(command, key);
            if (param != null) {
                String value = "true";
                if (param.isWithParamData()) {
                    if (j == arg.length() - 1 && i + 1 < args.size()) { // 最后一个短选项允许带参数数据
                        value = args.get(++i);
                    } else {
                        throw new MissParamDataException(command, param, "-" + key);
                    }
                }
                setValue(command, realParams, param, value);
            }
        }
        return i;
    }

    private int parseLongOpt(Command command, List<String> args, int i, Map<String, Object> realParams) {
        if (args.get(i).length() <= 2) {
            if (!command.getConfig().isIgnoreUnknownOpt()) {
                throw new UnknownOptException(command, args.get(i));
            }
            return i;
        }
        String arg = args.get(i).substring(2);
        if (arg.contains("=")) {
            String[] kv = StringUtil.splitKv(arg, "=");
            assert kv != null;
            OptParam param = findParamByLongOpt(command, kv[0]);
            if (param != null) {
                setValue(command, realParams, param, kv[1]);
            }
        } else {
            OptParam param = findParamByLongOpt(command, arg);
            if (param != null) {
                String value = "true";
                if (param.isWithParamData()) {
                    if (i + 1 >= args.size()) {
                        throw new MissParamDataException(command, param, "--" + arg);
                    }
                    value = args.get(++i);
                }
                setValue(command, realParams, param, value);
            }
        }
        return i;
    }

    private IndexParam findIndexParam(Command command, int index, String arg) {
        return findIndexParam(command, index, arg, command.getConfig().isIgnoreUnknownParam());
    }

    private IndexParam findIndexParam(Command command, int index, String arg, boolean ignoreUnknownParam) {
        List<IndexParam> indexParams = indexParamMap.get(command);
        List<IndexParam> multiples = multipleIndexParamMap.get(command);
        if (CollectionUtil.isEmpty(indexParams) || (index >= indexParams.size() && CollectionUtil.isEmpty(multiples))) {
            if (!ignoreUnknownParam) {
                throw new UnknownParamException(command.getName(), arg);
            }
            return null;
        }
        if (index < indexParams.size()) {
            return indexParams.get(index);
        }
        return multiples.get((index - indexParams.size()) % multiples.size());
    }

    private OptParam findParamByShortOpt(Command command, char key) {
        OptParam param = shortOptParamMap.getOrDefault(command, Collections.emptyMap()).get(key);
        if (param == null && !command.getConfig().isIgnoreUnknownOpt()) {
            throw new UnknownOptException(command, String.valueOf(key));
        }
        return param;
    }

    private OptParam findParamByLongOpt(Command command, String key) {
        OptParam param = longOptParamMap.getOrDefault(command, Collections.emptyMap()).get(key);
        if (param == null && !command.getConfig().isIgnoreUnknownOpt()) {
            throw new UnknownOptException(command, key);
        }
        return param;
    }

    private void setValue(Command command, Map<String, Object> realParams, CmdParam param, String value) {
        Object realValue = changeType(value, param.getType(), param.getGenericType());
        if (realValue == null) {
            return;
        }
        if (param.isMultiple()) {
            @SuppressWarnings("unchecked")
            List<Object> list = (List<Object>) realParams.computeIfAbsent(param.getName(), (key) -> new ArrayList<>());
            list.add(realValue);
        } else {
            if (realParams.containsKey(param.getName()) && !command.getConfig().isIgnoreRepeatParam()) {
                throw new RepeatParamException(command, param, realParams.get(param.getName()));
            }
            realParams.put(param.getName(), realValue);
        }
    }

    private Object changeType(String value, Class<?> requireType, Type genericType) {
        return ConvertUtil.to(requireType, genericType, value);
    }

    public void complete(SolonCommand cmd, SolonCommand.SolonMethodCommand subCmd, LineReader reader,
            ParsedLine line, List<Candidate> candidates) {
        loadCommandIfNotExist(subCmd);
        Map<String, Object> parsedParams = new HashMap<>();
        // 懒加载
        Supplier<Map<String, Object>> parseFunc = () -> {
            if (parsedParams.isEmpty()) {
                int subIndex = subCmd.getName().isEmpty() ? 1 : 2;
                parsedParams.putAll(parse(subCmd, CollectionUtil.subList(line.words(), subIndex),
                        line.wordIndex() - subIndex));
            }
            return parsedParams;
        };

        // 已使用的选项参数
        Set<String> usedLongOpts = new HashSet<>();
        Set<Character> usedShortOpts = new HashSet<>();
        for (int i = 0; i < line.words().size(); i++) {
            String p = line.words().get(i);
            if (p.startsWith("--") && i != line.wordIndex()) {
                int endIndex = p.indexOf('=');
                if (endIndex == -1) {
                    endIndex = p.length();
                }
                usedLongOpts.add(p.substring(2, endIndex));
            } else if (p.startsWith("-") && !p.startsWith("--")) {
                for (int j = 1; j < p.length(); j++) {
                    char c = p.charAt(j);
                    if (c == '=') {
                        break;
                    }
                    usedShortOpts.add(c);
                }
            }
        }

        // 情况1：当前单词是选项参数，补全参数key或者data
        String arg = line.word();
        if (StringUtil.isNotEmpty(arg) && arg.startsWith("-")) {
            if (arg.contains("=")) {
                if (completeOptData(cmd, subCmd, candidates, arg, line, parseFunc)) {
                    return;
                }
            }

            if (arg.startsWith("--") || arg.equals("-")) {
                if (arg.startsWith("--")) {
                    String opt = arg.substring(2);
                    OptParam p = longOptParamMap.getOrDefault(subCmd, Collections.emptyMap()).get(opt);
                    if (p != null && p.isWithParamData()) {
                        candidates.add(new Candidate(arg + "=", "=", null, "为参数[" + opt + "]传值", null, null,
                                false));
                        return;
                    }
                }
                completeLongOptKey(cmd, subCmd, line, candidates, arg, usedLongOpts, usedShortOpts);
            }
            if (!arg.startsWith("--")) {
                char lastArgOpt = arg.charAt(arg.length() - 1);
                if (lastArgOpt != '-') {
                    OptParam p =
                            shortOptParamMap.getOrDefault(subCmd, Collections.emptyMap()).get(lastArgOpt);
                    if (p != null && p.isWithParamData()) {
                        candidates.add(new Candidate(arg + "=", "=", null, "为参数[" + lastArgOpt + "]传值", null, null,
                                false));
                        return;
                    }
                }
                if (completeShortOptKey(cmd, subCmd, line, candidates, usedLongOpts, usedShortOpts)) {
                    candidates.add(new Candidate(line.word(), "", null, null, null, null, true));
                    return;
                }
            }
            return;
        }
        // 情况2：前一个参数是选项参数，且还需要一个参数，补全这个参数的data
        if (line.wordIndex() > 2 || ("".equals(subCmd.getName()) && line.wordIndex() == 2)) {
            String previousArg = line.words().get(line.wordIndex() - 1);
            if (previousArg.startsWith("-") && !previousArg.contains("=")) {
                OptParam param;
                if (previousArg.startsWith("--")) {
                    param = longOptParamMap.getOrDefault(subCmd, Collections.emptyMap()).get(previousArg.substring(2));
                } else {
                    param = shortOptParamMap.getOrDefault(subCmd, Collections.emptyMap()).get(previousArg.charAt(previousArg.length() - 1));
                }
                if (param != null && param.isWithParamData()) {
                    completeParamData(cmd, subCmd, param, arg, candidates, "", line, parseFunc);
                    return;
                }
            }
        }
        // 情况3：新加选项参数
        if (StringUtil.isEmpty(arg)) {
            completeShortOptKey(cmd, subCmd, line, candidates, usedLongOpts, usedShortOpts);
            completeLongOptKey(cmd, subCmd, line, candidates, arg, usedLongOpts, usedShortOpts);
        }

        // 情况4：index参数
        int startIndex = "".equals(subCmd.getName()) ? 1 : 2;
        int index = 0;
        for (int i = startIndex; i < line.wordIndex(); i++) {
            String argc = line.words().get(i);
            if (argc.startsWith("-")) {
                if (argc.contains("=")) {
                    continue;
                }
                if (argc.startsWith("--")) {
                    if (findParamByLongOpt(subCmd, argc.substring(2)).isWithParamData()) {
                        i++;
                    }
                } else {
                    if (findParamByShortOpt(subCmd, argc.charAt(argc.length() - 1)).isWithParamData()) {
                        i++;
                    }
                }
            } else {
                index++;
            }
        }
        IndexParam param = findIndexParam(subCmd, index, arg, true);
        if (param != null) {
            completeParamData(cmd, subCmd, param, arg, candidates, "", line, parseFunc);
        }

    }

    private boolean completeOptData(SolonCommand cmd, SolonCommand.SolonMethodCommand subCmd,
            List<Candidate> candidates,
            String arg, ParsedLine line, Supplier<Map<String, Object>> parseFunc) {
        String[] kv = StringUtil.splitKv(arg, "=");
        assert kv != null;
        OptParam param;
        if (kv[0].startsWith("--")) {
            param = longOptParamMap.getOrDefault(subCmd, Collections.emptyMap()).get(kv[0].substring(2));
        } else {
            param = shortOptParamMap.getOrDefault(subCmd, Collections.emptyMap()).get(kv[0].charAt(kv[0].length() - 1));
        }
        if (param != null && param.isWithParamData()) {
            completeParamData(cmd, subCmd, param, kv[1], candidates, kv[0] + "=", line, parseFunc);
            return true;
        }
        return false;
    }

    private void completeParamData(SolonCommand cmd, SolonCommand.SolonMethodCommand subCmd, CmdParam param,
            String currentData, List<Candidate> candidates, String prefix, ParsedLine line,
            Supplier<Map<String, Object>> parseFunc) {
        if (param.getCompleter() != null) {
            param.getCompleter().get().complete(cmd, subCmd, param, currentData, candidates, prefix, line, parseFunc);
        } else {
            DataCompleter completer = CompleterManager.getCompleter(param.getType());
            if (completer != null) {
                completer.complete(cmd, subCmd, param, currentData, candidates, prefix, line, parseFunc);
            } else {
                // 自动补全不是强制的
//                log.warn("unable to complete param data, cmd: {}, subCmd: {}, param: {}", cmd.getName(),
//                        subCmd.getName(), param.getName());
                candidates.add(new Candidate("", "输入参数的值<" + param.getName() + ">", null, param.getDescription(), null,
                        null, false));
            }
        }
    }
}
